import os
from typing import List, Optional, Union
from pathlib import Path
from PIL import Image
import mimetypes
from fastapi import HTTPException
import magic  # 需要安装 python-magic 库
import pythoncom
import win32com.client
import tempfile
import fitz  # PyMuPDF 库，用于 PDF 预览
import logging
import mammoth
from pptx import Presentation
import base64
from io import BytesIO
from bs4 import BeautifulSoup
import time
import pandas as pd
import io

logger = logging.getLogger(__name__)

class PreviewUtils:
    # 添加支持的文件类型和大小限制
    SUPPORTED_TEXT_TYPES = {'.txt', '.md', '.py', '.json', '.yaml', '.yml', '.log'}
    SUPPORTED_IMAGE_TYPES = {'.jpg', '.jpeg', '.png', '.gif', '.bmp'}
    MAX_TEXT_SIZE = 1024 * 1024  # 1MB
    MAX_IMAGE_SIZE = 5 * 1024 * 1024  # 5MB

    @staticmethod
    def validate_file_path(file_path: str) -> Path:
        """验证文件路径的安全性和有效性"""
        try:
            path = Path(file_path).resolve()
            if not path.exists():
                raise HTTPException(status_code=404, detail="File not found")
            if not path.is_file():
                raise HTTPException(status_code=400, detail="Path is not a file")
            if not os.access(path, os.R_OK):
                raise HTTPException(status_code=403, detail="Permission denied")
            return path
        except Exception as e:
            raise HTTPException(status_code=400, detail=str(e))

    @staticmethod
    def check_file_size(file_path: Path, max_size: int):
        """检查文件大小是否超过限制"""
        if file_path.stat().st_size > max_size:
            raise HTTPException(
                status_code=400, 
                detail=f"File size exceeds limit ({max_size/1024/1024}MB)"
            )

    @classmethod
    def get_file_type(cls, file_path: str) -> str:
        """获取文件类型，使用扩展名判断，对未知类型尝试作为文本文件读取"""
        try:
            # 主要使用扩展名判断
            ext = Path(file_path).suffix.lower()
            logger.info(f"Checking file type for: {file_path}, extension: {ext}")
            
            # 根据扩展名判断文件类型
            if ext in ['.txt', '.log', '.md', '.json', '.xml', '.yml', '.yaml', '.ini', '.conf', '.py', '.js', '.css', '.html']:
                return 'text'
            elif ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']:
                return 'image'
            elif ext == '.pdf':
                return 'pdf'
            elif ext in ['.doc', '.docx']:
                return 'word'
            elif ext in ['.xls', '.xlsx']:
                return 'excel'
            elif ext in ['.ppt', '.pptx']:
                return 'powerpoint'
            
            # 对于未知类型的文件，尝试作为文本文件读取
            try:
                # 只读取文件开头一小部分来判断是否为文本文件
                with open(file_path, 'r', encoding='utf-8') as f:
                    f.read(1024)  # 尝试读取开头部分
                    logger.info(f"Unknown extension but readable as text: {file_path}")
                    return 'text'
            except UnicodeDecodeError:
                # 尝试使用 GBK 编码
                try:
                    with open(file_path, 'r', encoding='gbk') as f:
                        f.read(1024)
                        logger.info(f"Unknown extension but readable as GBK text: {file_path}")
                        return 'text'
                except:
                    pass
            except:
                pass
            
            # 如果都不匹配，返回 unknown
            logger.warning(f"Unknown file type for: {file_path}")
            return 'unknown'
            
        except Exception as e:
            logger.error(f"Error getting file type: {str(e)}")
            return 'unknown'

    def create_text_preview(self, file_path: str, max_size: int = 1024 * 1024) -> str:
        """创建文本预览，支持多种编码，限制大小"""
        try:
            file_size = os.path.getsize(file_path)
            
            # 尝试不同的编码
            encodings = ['utf-8', 'gbk', 'gb2312', 'ascii']
            
            for encoding in encodings:
                try:
                    with open(file_path, 'r', encoding=encoding) as f:
                        if file_size > max_size:
                            content = f.read(max_size)
                            content += f"\n... (文件太大，仅显示前 {max_size//1024} KB 内容)"
                        else:
                            content = f.read()
                        logger.info(f"Successfully read file as {encoding}: {file_path}")
                        return content
                except UnicodeDecodeError:
                    continue
                except Exception as e:
                    logger.error(f"Error reading file with {encoding}: {str(e)}")
                    continue
            
            # 如果所有编码都失败了，返回错误信息
            return "无法识别文件编码，预览失败"
            
        except Exception as e:
            logger.error(f"Text preview error: {str(e)}")
            return f"预览失败: {str(e)}"

    @staticmethod
    def create_image_preview(file_path: str, max_size: tuple = (200, 200)) -> Optional[bytes]:
        try:
            with Image.open(file_path) as img:
                img.thumbnail(max_size)
                # 转换为bytes
                from io import BytesIO
                buffer = BytesIO()
                img.save(buffer, format=img.format)
                return buffer.getvalue()
        except Exception:
            return None 

    def create_office_preview(self, file_path: str) -> str:
        """创建 Office 文档预览"""
        try:
            ext = Path(file_path).suffix.lower()
            if ext in ['.doc', '.docx']:
                return self._create_word_preview(file_path)
            elif ext in ['.xls', '.xlsx']:
                return self._create_excel_preview(file_path)
            elif ext in ['.ppt', '.pptx']:
                return self._create_ppt_preview(file_path)
            else:
                return "不支持的文件类型"
        except Exception as e:
            logger.error(f"Error creating office preview: {str(e)}")
            return f"预览失败: {str(e)}"

    def _create_word_preview(self, file_path: str) -> str:
        """创建 Word 文档预览"""
        try:
            ext = Path(file_path).suffix.lower()
            
            # 如果是 doc 格式，先转换为 docx
            if ext == '.doc':
                docx_path = self._convert_doc_to_docx(file_path)
                if not docx_path:
                    return "DOC 文件转换失败"
                file_path = docx_path

            try:
                # 使用 mammoth 转换 docx 为 HTML
                with open(file_path, 'rb') as docx_file:
                    result = mammoth.convert_to_html(docx_file)
                html = result.value

                # 美化 HTML
                soup = BeautifulSoup(html, 'html.parser')
                html = soup.prettify()

                # 添加样式
                html = f"""
                <div class="word-preview">
                    <style>
                        .word-preview {{
                            font-family: Arial, sans-serif;
                            line-height: 1.6;
                            padding: 20px;
                        }}
                        .word-preview p {{ margin: 1em 0; }}
                        .word-preview h1 {{ font-size: 2em; margin: 1em 0; }}
                        .word-preview h2 {{ font-size: 1.5em; margin: 0.83em 0; }}
                        .word-preview table {{ 
                            width: 100%;
                            border-collapse: collapse;
                            margin: 1em 0;
                        }}
                        .word-preview td, .word-preview th {{ 
                            border: 1px solid #ddd;
                            padding: 8px;
                        }}
                        .word-preview img {{ max-width: 100%; height: auto; }}
                        .word-preview ul, .word-preview ol {{ padding-left: 2em; }}
                    </style>
                    {html}
                </div>
                """
                
                return html

            finally:
                # 如果是转换的临时文件，删除它
                if ext == '.doc' and file_path != str(Path(file_path)):
                    try:
                        os.remove(file_path)
                    except:
                        pass

        except Exception as e:
            logger.error(f"Word preview error: {str(e)}")
            return f"Word 文档预览失败: {str(e)}"

    def _convert_doc_to_docx(self, doc_path: str) -> Optional[str]:
        """将 doc 转换为 docx"""
        try:
            pythoncom.CoInitialize()
            word = win32com.client.Dispatch("Word.Application")
            word.Visible = False

            # 创建临时文件
            temp_dir = tempfile.gettempdir()
            docx_path = os.path.join(temp_dir, f"{os.path.splitext(os.path.basename(doc_path))[0]}.docx")

            try:
                # 打开并另存为 docx
                doc = word.Documents.Open(doc_path)
                doc.SaveAs2(docx_path, FileFormat=16)  # 16 表示 docx 格式
                doc.Close()
                return docx_path

            except Exception as e:
                logger.error(f"Doc to docx conversion error: {str(e)}")
                return None

            finally:
                word.Quit()
                pythoncom.CoUninitialize()

        except Exception as e:
            logger.error(f"Word initialization error: {str(e)}")
            return None

    def _create_ppt_preview(self, file_path: str) -> str:
        """创建 PPT 预览"""
        try:
            ext = Path(file_path).suffix.lower()
            
            # 如果是 ppt 格式，先转换为 pptx
            if ext == '.ppt':
                pptx_path = self._convert_ppt_to_pptx(file_path)
                if not pptx_path:
                    return "PPT 文件转换失败"
                file_path = pptx_path

            try:
                prs = Presentation(file_path)
                content = ['<div class="ppt-preview">']
                
                for i, slide in enumerate(prs.slides, 1):
                    content.append(f'<div class="slide">')
                    content.append(f'<div class="slide-header">第 {i} 页 / 共 {len(prs.slides)} 页</div>')
                    
                    # 提取文本和图片
                    for shape in slide.shapes:
                        # 处理文本
                        if hasattr(shape, "text") and shape.text.strip():
                            text = shape.text.strip()
                            # 根据文本框类型添加不同的样式
                            if hasattr(shape, "text_frame") and shape.text_frame.paragraphs:
                                first_para = shape.text_frame.paragraphs[0]
                                if first_para.level == 0:  # 标题
                                    content.append(f'<h2>{text}</h2>')
                                else:  # 正文
                                    content.append(f'<p>{text}</p>')
                            else:
                                content.append(f'<p>{text}</p>')
                        
                        # 处理图片
                        if shape.shape_type == 13:  # MSO_SHAPE_TYPE.PICTURE
                            try:
                                image_data = self._extract_image_from_shape(shape)
                                if image_data:
                                    content.append(
                                        f'<div class="slide-image"><img src="data:image/png;base64,{image_data}" alt="Slide {i} Image"/></div>'
                                    )
                            except:
                                continue
                    
                    content.append('</div>')
                
                content.append('</div>')
                
                # 添加样式
                html = f"""
                <style>
                    .ppt-preview {{
                        font-family: Arial, sans-serif;
                        padding: 20px;
                    }}
                    .slide {{
                        margin-bottom: 40px;
                        padding: 30px;
                        border: 1px solid #ddd;
                        border-radius: 8px;
                        background: white;
                        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                    }}
                    .slide-header {{
                        color: #666;
                        font-size: 14px;
                        margin-bottom: 20px;
                        text-align: right;
                    }}
                    .slide h2 {{
                        color: #333;
                        font-size: 24px;
                        margin: 0 0 20px 0;
                    }}
                    .slide p {{
                        color: #444;
                        font-size: 16px;
                        line-height: 1.6;
                        margin: 10px 0;
                    }}
                    .slide-image {{
                        margin: 20px 0;
                        text-align: center;
                    }}
                    .slide-image img {{
                        max-width: 100%;
                        height: auto;
                        border-radius: 4px;
                    }}
                </style>
                {''.join(content)}
                """
                
                return html

            finally:
                # 如果是转换的临时文件，删除它
                if ext == '.ppt' and file_path != str(Path(file_path)):
                    try:
                        os.remove(file_path)
                    except:
                        pass

        except Exception as e:
            logger.error(f"PPT preview error: {str(e)}")
            return f"PPT 预览失败: {str(e)}"

    def _convert_ppt_to_pptx(self, ppt_path: str) -> Optional[str]:
        """将 ppt 转换为 pptx"""
        powerpoint = None
        try:
            # 确保路径是绝对路径
            ppt_path = str(Path(ppt_path).resolve())
            
            # 初始化 COM
            pythoncom.CoInitialize()
            
            # 尝试多次启动 PowerPoint
            for _ in range(3):  # 最多尝试3次
                try:
                    powerpoint = win32com.client.Dispatch("PowerPoint.Application")
                    powerpoint.Visible = False
                    break
                except Exception as e:
                    logger.warning(f"PowerPoint initialization attempt failed: {str(e)}")
                    if powerpoint:
                        try:
                            powerpoint.Quit()
                        except:
                            pass
                    pythoncom.CoUninitialize()
                    pythoncom.CoInitialize()
                    continue
            
            if not powerpoint:
                return None

            # 创建临时文件
            temp_dir = tempfile.gettempdir()
            pptx_path = os.path.join(temp_dir, f"{os.path.splitext(os.path.basename(ppt_path))[0]}_{int(time.time())}.pptx")

            try:
                # 打开并另存为 pptx
                presentation = powerpoint.Presentations.Open(ppt_path, WithWindow=False)
                presentation.SaveAs(pptx_path, 24)  # 24 表示 pptx 格式
                presentation.Close()
                return pptx_path

            except Exception as e:
                logger.error(f"PPT to PPTX conversion error: {str(e)}")
                # 如果转换失败，尝试直接使用 python-pptx 读取
                try:
                    if Path(ppt_path).suffix.lower() == '.pptx':
                        return ppt_path
                except:
                    pass
                return None

            finally:
                try:
                    if powerpoint:
                        powerpoint.Quit()
                except:
                    pass

        except Exception as e:
            logger.error(f"PowerPoint initialization error: {str(e)}")
            return None

        finally:
            try:
                pythoncom.CoUninitialize()
            except:
                pass

    def _image_to_base64(self, image) -> dict:
        """将图片转换为 base64"""
        with image.open() as img:
            # 转换为 PNG 格式
            if img.format != 'PNG':
                buffer = BytesIO()
                img.convert('RGB').save(buffer, format='PNG')
                img_data = buffer.getvalue()
            else:
                img_data = image.read()
            
            return {
                "src": f"data:image/png;base64,{base64.b64encode(img_data).decode()}"
            }

    def _extract_image_from_shape(self, shape) -> str:
        """从 PPT 形状中提取图片"""
        try:
            image_stream = BytesIO(shape.image.blob)
            with Image.open(image_stream) as img:
                buffer = BytesIO()
                img.convert('RGB').save(buffer, format='PNG')
                return base64.b64encode(buffer.getvalue()).decode()
        except:
            return None

    def create_pdf_preview(self, file_path: str) -> str:
        """创建 PDF 预览"""
        try:
            doc = fitz.open(file_path)
            content = []
            
            # 只预览前几页
            max_pages = min(5, doc.page_count)
            for page_num in range(max_pages):
                page = doc[page_num]
                content.append(f"=== 第 {page_num + 1} 页 ===\n")
                content.append(page.get_text())
                content.append("\n")
            
            if doc.page_count > max_pages:
                content.append(f"\n... (共 {doc.page_count} 页，仅显示前 {max_pages} 页)")
            
            doc.close()
            return "\n".join(content)
        except Exception as e:
            return f"PDF 预览失败: {str(e)}"

    def _create_excel_preview(self, file_path: str) -> str:
        """创建 Excel 预览"""
        try:
            logger.info(f"Starting Excel preview for file: {file_path}")
            ext = Path(file_path).suffix.lower()
            
            try:
                # 读取 Excel 数据
                df = self._read_excel_file(file_path, ext)
                if isinstance(df, str):  # 如果返回错误信息
                    return df
                
                # 生成 HTML 表格
                html_table = self._generate_excel_table(df)
                
                # 添加样式和布局
                html = self._wrap_excel_html(html_table)
                
                return html
                
            except Exception as e:
                logger.error(f"Error reading Excel file: {str(e)}")
                return f"无法读取 Excel 文件: {str(e)}"
                
        except Exception as e:
            logger.error(f"Excel preview error: {str(e)}")
            return f"Excel 预览失败: {str(e)}"

    def _read_excel_file(self, file_path: str, ext: str) -> Union[pd.DataFrame, str]:
        """读取 Excel 文件内容"""
        try:
            if ext == '.xls':
                try:
                    # 使用 xlrd 1.2.0 读取 xls 文件
                    import xlrd
                    logger.info("Using xlrd engine for .xls file")
                    workbook = xlrd.open_workbook(file_path)
                    sheet = workbook.sheet_by_index(0)  # 获取第一个工作表
                    
                    # 转换为 DataFrame
                    data = []
                    for row_idx in range(min(sheet.nrows, 100)):  # 限制行数
                        row_data = []
                        for col_idx in range(sheet.ncols):
                            cell = sheet.cell(row_idx, col_idx)
                            value = self._process_cell_value(cell, workbook.datemode)
                            row_data.append(value)
                        data.append(row_data)
                    
                    if len(data) > 0:
                        headers = data[0]
                        return pd.DataFrame(data[1:], columns=headers)
                    return pd.DataFrame()
                    
                except ImportError:
                    logger.warning("xlrd not available, falling back to pandas with openpyxl")
                    return pd.read_excel(file_path, engine='openpyxl', nrows=100)
            else:
                # xlsx 文件使用 openpyxl
                logger.info("Using openpyxl engine for .xlsx file")
                return pd.read_excel(file_path, engine='openpyxl', nrows=100)
                
        except Exception as e:
            logger.error(f"Error reading Excel file: {str(e)}")
            return f"无法读取 Excel 文件: {str(e)}"

    def _process_cell_value(self, cell, datemode: int) -> str:
        """处理单元格值"""
        import xlrd
        try:
            if cell.ctype == xlrd.XL_CELL_DATE:
                try:
                    # 处理日期类型
                    value = xlrd.xldate.xldate_as_datetime(cell.value, datemode)
                    return value.strftime('%Y-%m-%d %H:%M:%S')
                except:
                    return str(cell.value)
            elif cell.ctype == xlrd.XL_CELL_NUMBER:
                # 处理数字类型
                return str(int(cell.value) if cell.value.is_integer() else cell.value)
            else:
                return str(cell.value)
        except:
            return str(cell.value)

    def _generate_excel_table(self, df: pd.DataFrame) -> str:
        """生成 Excel HTML 表格"""
        # 处理数据格式
        df = df.fillna('')
        
        # 将数据转换为 HTML 表格
        return df.to_html(
            classes=['excel-table', 'hover-table'],
            index=False,
            na_rep='',
            escape=True,
            border=0
        )

    def _wrap_excel_html(self, html_table: str) -> str:
        """添加 HTML 样式和布局"""
        return f"""
        <div class="excel-preview-wrapper">
            <style>
                .excel-preview-wrapper {{
                    width: 100%;
                    height: 100%;
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    padding: 20px;
                    box-sizing: border-box;
                    background: #f5f7fa;
                }}
                .excel-preview {{
                    width: 80%;
                    height: 80%;
                    background: #fff;
                    border-radius: 8px;
                    overflow: auto;
                    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
                    padding: 20px;
                }}
                .excel-table {{
                    width: auto;
                    min-width: 100%;
                    border-collapse: collapse;
                    font-size: 14px;
                    table-layout: fixed;
                }}
                .excel-table th,
                .excel-table td {{
                    width: 200px;
                    min-width: 200px;
                    max-width: 200px;
                    padding: 12px 8px;
                    border: 1px solid #ebeef5;
                    white-space: nowrap;
                    overflow: hidden;
                    text-overflow: ellipsis;
                }}
                .excel-table thead {{
                    background-color: #f8f9fb;
                }}
                .excel-table th {{
                    position: sticky;
                    top: 0;
                    z-index: 2;
                    font-weight: 600;
                    color: #606266;
                    text-align: left;
                    background: #f8f9fb;
                }}
                .excel-table tr:hover td {{
                    background-color: #f5f7fa;
                }}
                .excel-table td:hover {{
                    position: relative;
                    z-index: 1;
                    overflow: visible;
                    white-space: normal;
                    background-color: #ecf5ff !important;
                    box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
                    border-radius: 4px;
                }}
                .table-info {{
                    padding: 8px 16px;
                    background: #f8f9fb;
                    border-top: 1px solid #ebeef5;
                    color: #909399;
                    font-size: 12px;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                }}
            </style>
            <div class="excel-preview">
                {html_table}
                <div class="table-info">
                    <span>提示：鼠标悬停可查看完整内容</span>
                    <span>可左右滚动查看更多列</span>
                </div>
            </div>
        </div>
        """